<?php
namespace Lib {
    
    /**
     * Class DI
     * @package Lib
     * @property-read array $test 
     */
    final class DI {
        static protected $diInstance=null;
        
        protected $_classDependencies = [];
        protected $_implments = [];
        protected $_service = [];
        protected $_container = [];
        protected $_analysed = [];
        
        public function __construct() {
            if(self::$diInstance) {
                throw new \Exception("请不要实例化 ". DI::class. " , 使用 di() 函数代替");
            }
            
            $diConfigFile = dirname(__DIR__) . '/config/di.php';
            $interfaceInjection = [];
            
            if(file_exists($diConfigFile)) {
                $interfaceInjection = @include $diConfigFile; 
            }
            
            $this->_implments = array_merge($this->_implments, $interfaceInjection);
            self::$diInstance = $this;
        }
    
        /**
         * 从容器中获取共享的服务
         * @param $class
         * @param array $params
         * @return mixed
         * @throws \Exception
         */
        public function get($class, $params=[]) {
            $class = '\\' . ltrim($class, '\\');
            
            // 从已经注册的服务中查找
            if($this->_service[$class]) {
                return $this->_service[$class];
            }
            
            // 尚未注册的服务或者依赖实现, 创建对应的依赖实现
            $instance = $this->make($class, $params);
            $this->_service[$class] = $instance;
            
            return $instance;
        }
    
        /**
         * 创建容器中的对应实现, 并注入所需要的依赖
         * @param $class
         * @param array $params
         * @return mixed
         * @throws \ReflectionException
         */
        public function make($class, $params=[]) {
            $class = '\\' . ltrim($class, '\\');
    
            //
            // 如果是一个接口或者抽象类, 使用手动配置的依赖实现关系
            // 来找到对应的实现
            // 如果是已实现的类, 查找它的重写
            //
            $queryedClass = [];
            while($impClassInfo = $this->_implments[$class]) {
                if($queryedClass[$class]) {
                    break;
                }
                
                $queryedClass[$class] = 1;
                $class = $impClassInfo['class'] ? $impClassInfo['class'] : $class;
                $params = $params ? $params : ((array)$impClassInfo['params']);
            }

            $rc = new \ReflectionClass($class);

            if($rc->isInterface() || $rc->isAbstract()) {
                throw new \Exception("没有找到 $class 的对应实现方式");
            }
            
            $dependencies = $this->_classDependencies[$class];
            
            // 如果对象没有设置依赖, 则分析类中的所有依赖
            if(!isset($dependencies)) {
                $dependencies = $this->_analyseDependencies($class);
                $this->_classDependencies[$class] = $dependencies;
            }
            
            return new $class(...$params);
        }
        
        public function getDependencies($class) {
            $class = '\\' . ltrim($class, '\\');
            return $this->_classDependencies[$class];
        }
        
        public function getImplments($interface) {
            $class = '\\' . ltrim($interface, '\\');
            return $this->_implments[$interface];
        }
        
        protected function _analyseDependencies($class) {
            $rc = new \ReflectionClass($class);
            $dependencies = [];
            
            while (true) {
                $curDeps = $this->_analyseDependency($rc);
                $dependencies []= $curDeps;
                $rc = $rc->getParentClass();
                
                if(!$rc) {
                    break;
                }
            }
            
            return array_reduce($dependencies, function ($cur, $next) {
                return array_merge($cur, $next);
            }, []);
        }
    
        protected function _analyseDependency(\ReflectionClass $rc) {
            $className = $rc->getName();
            if(isset($this->_analysed [$className])) {
                return $this->_analysed[$className];
            }
            
            $doc = $rc->getDocComment();
            
            if(empty($doc)) {
                return $this->_analysed[$className] = [];
            }
            
            preg_match_all('#\* *@property +(.*)\n#', $doc, $rwMatchs);
            preg_match_all('#\* *@property-read +(.*)\n#', $doc, $rMatchs);
            $deps = [];
            
            $add = function ($write=false) use(&$deps) {
                return function ($propdoc) use($write, &$deps) {
                    $propdoc = str_replace("\t", ' ', $propdoc);
                    list($class, $var) = array_filter(
                        array_map('trim', explode(' ',$propdoc))
                    );
    
                    if($class[0] == '\\' && !in_array($class, ['\Closure']) && $var[0] == '$') {
                        $var = ltrim($var, '$');
                        $deps[$var] = ['class' => $class, 'write' => $write];
                    }
                };
            };
            
            array_map($add(true), $rwMatchs[1]);
            array_map($add(false), $rMatchs[1]);
            $this->_analysed[$className] = $deps;
            
            return $deps;
        }
    }
}

namespace {
    /**
     * 依赖注入容器入口
     * @return \Lib\DI
     */
    function di() {
        static $di;

        if(null == $di) {
            $di = new \Lib\DI();
        }

        return $di;
    }
}

